/*
 * Copyright 2003 Program of Computer Graphics, Cornell University
 *     580 Rhodes Hall
 *     Cornell University
 *     Ithaca NY 14853
 * Web: http://www.graphics.cornell.edu/
 * 
 * Not for commercial use. Do not redistribute without permission.
 */

//use double precision for triangle intersection code (slower but higher precision)
//#define DOUBLE_PRECISION_TRIANGLES

// Canonical Java class name for BRITE, with '$' and '.' replaced by '_'
// e.g.  germ_ray_accel_BriteNativeRayEngine
#if !defined(BRITE_JNI_CLASSNAME)
#error "Missing definition for BRITE_JNI_CLASSNAME"
#endif

// Macros to generate full JNI method names
#define BRITE_JNI_METHOD_IMPL1(classname, name) Java_ ## classname ## _ ## name
#define BRITE_JNI_METHOD_IMPL2(clazz, name) BRITE_JNI_METHOD_IMPL1(clazz, name)
#define BRITE_JNI_METHOD(name) BRITE_JNI_METHOD_IMPL2(BRITE_JNI_CLASSNAME,name)

#include "Scene.h"
#ifdef DOUBLE_PRECISION_TRIANGLES
#include "TriangleDP.h"
#else
#include "Triangle.h"
#endif
#include "Parallelogram.h"
#include "Disk.h"
#include "Sphere.h"
#include "BriteDefs.h"

#include <jni.h>
#include <cfloat>

// As of JDK 7, JNIEXPORT does not work with gcc's visibility attribute.
// While some JDK distributions do support it (e.g. Ubuntu) we will redefine
// the macro as a workaround
#if !defined(__has_attribute)
# define __has_attribute(x) 0
#endif
#if (defined(__GNUC__) && (__GNUC__ > 3)) || __has_attribute(visibility)
# undef  JNIEXPORT
# define JNIEXPORT __attribute__((visibility("default")))
#endif



#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    initInstance
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL BRITE_JNI_METHOD(initInstance)
  (JNIEnv *, jclass)
{
  Scene *scene = new Scene();
  return (jlong)scene;
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    addTriangleNative
 * Signature: (JIFFFFFFFFF)V
 */
JNIEXPORT void JNICALL BRITE_JNI_METHOD(addTriangleNative)
  (JNIEnv *, jclass, jlong thisptr, jint geomId, jfloat v0x, jfloat v0y, jfloat v0z, jfloat v1x, jfloat v1y, jfloat v1z, jfloat v2x, jfloat v2y, jfloat v2z)
{
  Scene *scene = (Scene *)thisptr;
  PoolAllocator *geomPool = scene->getGeometryObjectPool();
#ifdef DOUBLE_PRECISION_TRIANGLES
  TriangleDP *t = TriangleDP::poolAlloc(geomPool);
  t->set(v0x,v0y,v0z,v1x,v1y,v1z,v2x,v2y,v2z,geomId);
#else
  Triangle *t = Triangle::poolAlloc(geomPool);
  t->set(v0x,v0y,v0z,v1x,v1y,v1z,v2x,v2y,v2z,geomId);
#endif
  scene->addGeometryObject(t);
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    addRectangleNative
 * Signature: (JIFFFFFFFFFFFF)V
 */
JNIEXPORT void JNICALL BRITE_JNI_METHOD(addRectangleNative)
  (JNIEnv *, jclass, jlong thisptr, jint geomId, jfloat v0x, jfloat v0y, jfloat v0z, jfloat v1x, jfloat v1y, jfloat v1z, jfloat v2x, jfloat v2y, jfloat v2z, jfloat v3x, jfloat v3y, jfloat v3z)
{
  Scene *scene = (Scene *)thisptr;
  PoolAllocator *geomPool = scene->getGeometryObjectPool();
  Parallelogram *p = Parallelogram::poolAlloc(geomPool);
  p->set(v0x,v0y,v0z,v1x,v1y,v1z,v2x,v2y,v2z,v3x,v3y,v3z,geomId);
  scene->addGeometryObject(p);
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    addDiskNative
 * Signature: (JIFFFFFFFFFF)V
 */
JNIEXPORT void JNICALL BRITE_JNI_METHOD(addDiskNative)
  (JNIEnv *, jclass, jlong thisptr, jint geomId, jfloat centerX, jfloat centerY, jfloat centerZ, 
  jfloat normalX, jfloat normalY, jfloat normalZ, jfloat uAxisX, jfloat uAxisY, jfloat uAxisZ, jfloat radius)
{
  Scene *scene = (Scene *)thisptr;
  PoolAllocator *geomPool = scene->getGeometryObjectPool();
  Disk *d = Disk::poolAlloc(geomPool);
  d->set(centerX,centerY,centerZ,normalX,normalY,normalZ,uAxisX,uAxisY,uAxisZ,radius,geomId);
  scene->addGeometryObject(d);
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    addSphereNative
 * Signature: (JIFFFFFFFFFF)V
 */
JNIEXPORT void JNICALL BRITE_JNI_METHOD(addSphereNative)
  (JNIEnv *, jclass, jlong thisptr, jint geomId, jfloat centerX, jfloat centerY, jfloat centerZ, 
  jfloat uAxisX, jfloat uAxisY, jfloat uAxisZ, jfloat wAxisX, jfloat wAxisY, jfloat wAxisZ, jfloat radius)
{
  Scene *scene = (Scene *)thisptr;
  PoolAllocator *geomPool = scene->getGeometryObjectPool();
  Sphere *s = Sphere::poolAlloc(geomPool);
  s->set(centerX,centerY,centerZ,uAxisX,uAxisY,uAxisZ,wAxisX,wAxisY,wAxisZ,radius,geomId);
  scene->addGeometryObject(s);
}


/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    buildAccelerationStructure
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL BRITE_JNI_METHOD(buildAccelerationStructure)
  (JNIEnv *, jclass, jlong thisptr, jboolean printStats)
{
  Scene *scene = (Scene *)thisptr;
//_asm int 3; //force debugger breakpoint
//  scene->buildFlatList();
//  scene->buildNaiveMiddleSplit(5);
  //building the tree deletes the uncompiled geometry automatically
  scene->buildGreedySplit();
  if (printStats) scene->printTreeStats();
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    deleteAll
 * Signature: (J)V
 */
JNIEXPORT void JNICALL BRITE_JNI_METHOD(deleteAll)
  (JNIEnv *, jclass, jlong thisptr)
{
  Scene *scene = (Scene *)thisptr;
  scene->deleteAll();
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    findFirstIntersectionNative
 * Signature: (JJDDDDDDDD)I
 */
JNIEXPORT jint JNICALL BRITE_JNI_METHOD(findFirstIntersectionNative)
  (JNIEnv *env, jclass, jlong thisptr, jlong bufferPtr, jdouble originX, jdouble originY, jdouble originZ, jdouble dirX, jdouble dirY, jdouble dirZ, jdouble tStart, jdouble tEnd)
{
  double TMAX = 0.1*FLT_MAX;
//  SSEInexact();
  Scene *scene = (Scene *)thisptr;
  Ray ray;
  //check normalization of ray direction because Brite requires normalized ray directions
  double dirLength2 = dirX*dirX + dirY*dirY + dirZ*dirZ;
  ray.setOrigin((float)originX,(float)originY,(float)originZ);
  //prevent overflow for unbounded rays (start or end of ray is unbounded 
  tStart = (tStart<-TMAX)?-TMAX:tStart;
  tEnd = (tEnd > TMAX)?TMAX:tEnd;
  ray.setTParameters((float) (tStart), (float)(tEnd));
  ray.setDirection((float)(dirX),(float)(dirY),(float)(dirZ));
  IntersectionRecord hitRec;
  hitRec.primitiveId = -1;
  if (fabs(dirLength2 - 1.0) > 1e-4) {
    jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
    if (newExcCls == 0) { /* Unable to find the new exception class, give up. */
      BRITE_DEBUGBREAK; //force debugger breakpoint
      return 0x7FFFFFFF; //return max int, should cause an exception
    }
    env->ThrowNew(newExcCls, "Brite can only handled normalized rays");
  }
  scene->intersect(ray,&hitRec);
  if (hitRec.primitiveId >= 0) {
    float *buf = (float *)bufferPtr;
    buf[0] = hitRec.t;
    buf[1] = hitRec.u;
    buf[2] = hitRec.v;
  }
  return hitRec.primitiveId;
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    findFirstIntersectionBufferNative
 * Signature: (JJ)I
 */
JNIEXPORT jint JNICALL BRITE_JNI_METHOD(findFirstIntersectionBufferNative)
  (JNIEnv *env, jclass, jlong thisPtr, jlong bufferPtr)
{
  float TMAX = 0.1*FLT_MAX;
//  SSEInexact();
  Scene *scene = (Scene *)thisPtr;
  Ray ray;
  double *raybuf = (double *)bufferPtr;
  double originX = raybuf[0];
  double originY = raybuf[1];
  double originZ = raybuf[2];
  double dirX = raybuf[3];
  double dirY = raybuf[4];
  double dirZ = raybuf[5];
  double tStart = raybuf[6];
  double tEnd = raybuf[7];
  //check normalization of ray direction because Brite requires normalized ray directions
  double dirLength2 = dirX*dirX + dirY*dirY + dirZ*dirZ;
  ray.setOrigin((float)originX,(float)originY,(float)originZ);
  //prevent overflow for unbounded rays (start or end of ray is unbounded 
  tStart = (tStart<-TMAX)?-TMAX:tStart;
  tEnd = (tEnd > TMAX)?TMAX:tEnd;
  ray.setTParameters((float) (tStart), (float)(tEnd));
  ray.setDirection((float)(dirX),(float)(dirY),(float)(dirZ));
  IntersectionRecord hitRec;
  hitRec.primitiveId = -1;
  if (fabs(dirLength2 - 1.0) > 1e-4) {
    jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
    if (newExcCls == 0) { /* Unable to find the new exception class, give up. */
      BRITE_DEBUGBREAK; //force debugger breakpoint
      return 0x7FFFFFFF; //return max int, should cause an exception
    }
    env->ThrowNew(newExcCls, "Brite can only handled normalized rays");
  }
  scene->intersect(ray,&hitRec);
  if (hitRec.primitiveId >= 0) {
    float *buf = (float *)bufferPtr;
    buf[0] = hitRec.t;
    buf[1] = hitRec.u;
    buf[2] = hitRec.v;
  }
  return hitRec.primitiveId;
}

/*
 * Class:     germ_ray_accel_BriteNativeRayEngine
 * Method:    getBoundingBoxNative
 * Signature: (JJ)V
 */
JNIEXPORT void JNICALL BRITE_JNI_METHOD(getBoundingBoxNative)
  (JNIEnv *, jclass, jlong thisptr, jlong bufferPtr)
{
  Scene *scene = (Scene *)thisptr;
  float *buf = (float *)bufferPtr;
  AxisAlignedBox bounds = scene->getBoundingBox();
  buf[0] = bounds.min[0];
  buf[1] = bounds.min[1];
  buf[2] = bounds.min[2];
  buf[3] = bounds.max[0];
  buf[4] = bounds.max[1];
  buf[5] = bounds.max[2];
}

#ifdef __cplusplus
}
#endif
